/**
 * @file maze.js, a maze generator.
 *
 * @author nobu <nobu@ctmk.amanek.jp>
 */

(function(define) {
define([], function() {
	/**
	* @constructor
	*/
	var Maze = function() {
		// initialize
	};
	
	
	/**
	 * @const
	 * Types of Cell
	 */
	Maze.CellType = {
		WALL: 0,
		PATH: 1
	};
	
	/**
	 * @class
	 */
	Maze.Config = function () {
		this.need_data = true;
		this.need_process = false;
		this.width = 0;
		this.height = 0;
	};
	
	/**
	 * @class
	 * @param {Maze.Config} config
	 * @param {string} generator - Signature of maze generator which generates this result.
	 */
	Maze.Result = function(config, generator) {
		if (config.need_data) {
			this.data = [];
		}
		if (config.need_process) {
			this.celltype_to_fill = Maze.CellType.WALL;
			this.process = [];
		}
		this.width = config.width;
		this.height = config.height;
		this.generated_by = generator;
	};
	
	/**
	 * Generate a maze.
	 * @param {Class} algorithm
	 * @param {Maze.Config} config
	 */
	Maze.generate = function(algorithm, config) {
		return algorithm.generate(config);
	};
	
	/**
	 * Extend a instance of algorithm for maze.js to provide common function.
	 * @param {Class} algorithm
	 */
	Maze.extend_algorithm = function(algorithm) {
		algorithm.CellType = Maze.CellType;
		algorithm.Config = Maze.Config;
		algorithm.Result = Maze.Result;
		algorithm.get_data_filled = get_data_filled;
		algorithm.get_data_filled_with_edges = get_data_filled_with_edges;
		algorithm.make_step = make_step;
		algorithm.get_process_filling_edges = get_process_filling_edges;
		algorithm.put_without_process = put_without_process;
		algorithm.put_with_process = put_with_process;
		algorithm.add_process = add_process;
	};
	
	return Maze;

	
	/**
	 * @return {Array.<Array.<Maze.CellType>>}
	 * @param {number} width
	 * @param {number} height
	 * @param {Maze.CellType} celltype_to_fill
	 */
	function get_data_filled(width, height, celltype_to_fill) {
		var data = [];
		for (var y = 0; y < height; y += 1) {
			data[y] = [];
			for (var x = 0; x < width; x += 1) {
				data[y][x] = celltype_to_fill;
			}
		}
		return data;
	}
	
	/**
	 * @return {Array.<Array.<Maze.CellType>>}
	 * @param {number} width
	 * @param {number} height
	 * @param {Maze.CellType} celltype_to_fill
	 * @param {Maze.CellType} celltype_of_edge
	 */
	function get_data_filled_with_edges(width, height, celltype_to_fill, celltype_of_edge) {
		var data = [];
		var x, y;

		data[0] = [];
		for (x = 0; x < width; x += 1) {
			data[0][x] = celltype_of_edge;
		}

		for (y = 1; y < height-1; y += 1) {
			data[y] = [];
			data[y][0] = celltype_of_edge;
			for (x = 1; x < width-1; x += 1) {
				data[y][x] = celltype_to_fill;
			}
			data[y][x] = celltype_of_edge;
		}

		data[y] = [];
		for (x = 0; x < width; x += 1) {
			data[y][x] = celltype_of_edge;
		}

		return data;
	}
	
	/**
	 * Make a step of process.
	 * @return {{x: number, y: number, celltype: Maze.CellType}}
	 * @param {number} x
	 * @param {number} y
	 * @param {Maze.CellType} celltype
	 */
	function make_step(x, y, celltype) {
		return { x: x, y: y, celltype: celltype };
	}
	
	/**
	 * Make steps which fill the edges.
	 * @return {Array.<{{x: number, y: number, celltype: Maze.CellType}}>}
	 * @param {number} width
	 * @param {number} height
	 * @param {Maze.CellType} celltype
	 */
	function get_process_filling_edges(width, height, celltype_to_fill) {
		var process = [];
		(function () {
			var y = 0;
			for (var x = 0; x < width; x += 1) {
				process.push(make_step(x, y, celltype_to_fill));
			}
		})();
		(function () {
			var x = width - 1;
			for (var y = 1; y < height; y += 1) {
				process.push(make_step(x, y, celltype_to_fill));
			}
		})();
		(function () {
			var y = height - 1;
			for (var x = width-2; x >= 0; x -= 1) {
				process.push(make_step(x, y, celltype_to_fill));
			}
		})();
		(function () {
			var x = 0;
			for (var y = height-2; y >= 0; y -= 1) {
				process.push(make_step(x, y, celltype_to_fill));
			}
		})();
		return process;
	}

	/**
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 * @param {Maze.CellType} celltype
	 */
	function put_without_process(work, x, y, celltype) {
		work.data[y][x] = celltype;
	}
	
	/**
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 * @param {Maze.CellType} celltype
	 */
	function put_with_process(work, x, y, celltype) {
		work.data[y][x] = celltype;
		add_process(work, x, y, celltype);
	}
	
	/**
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 * @param {Maze.CellType} celltype
	 */
	function add_process(work, x, y, celltype) {
		work.process.push(make_step(x, y, celltype));
	}


});
})(typeof define !== 'undefined' ?
	// use define for AMD if available
	define :
	// If no define, look for module to export as a CommonJS module.
	typeof module !== 'undefined' ?
	function(deps, factory) { module.exports = factory(); } :
	// If no define or module, attach to current context.
	function(deps, factory) { this.Maze = factory(); }
);
